#!/bin/sh
#
# ssgs -- A simple static blog generator written in POSIX sh.
#
# Copyright (C) 2021-2024  Ricardo García Jiménez <ricardogj08@riseup.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

SSGS_VERSION=3.2
SSGS_REPO=https://notabug.org/ricardogj08/ssgs

# Imprime mensajes de errores.
error() {
  printf '%s.\n' "$1" >&2
  exit 1
}

# Imprime un mensaje de ayuda.
shelp() { printf '%s' "\
ssgs ${SSGS_VERSION} - A simple static blog generator written in POSIX sh.

Synopsis:
  ssgs [OPTION]

Options:
  -b - Build blog.
"
exit 0
}

# Convierte un archivo Markdown a HTML.
markdown_to_html() {
  smu "$1" 2>/dev/null
}

# Obtiene el título de un string en HTML.
get_title() {
  title=$(printf '%s\n' "$1" | grep -se '^<h1>' | head -n 1)

  title=${title#<h1>}
  title=${title%</h1>}

  printf '%s\n' "${title}"

  unset -v title
}

# Genera un enlace HTML.
anchor() {
  printf '<a href="%s">%s</a>\n' "$1" "$2"
}

# Genera el navbar del blog.
navbar() {
  anchor "${SSGS_URL}/index.html" "${SSGS_NEWS}"
  anchor "${SSGS_URL}/posts.html" "${SSGS_ALL}"
  anchor "${SSGS_URL}/atom.xml" "${SSGS_FEED}"

  printf '%s\n' "${SSGS_NAVBAR}" | markdown_to_html -- | sed 's/<\/\?p>//g'
}

# Plantilla base para todas las páginas del blog.
template() { cat <<EOF
<!DOCTYPE html>
<html lang="${SSGS_LANG}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="${SSGS_CSS}">
  <link rel="icon" href="${SSGS_FAVICON}">
  <title>${POST_TITLE}</title>
  <style>
    .markdown-body {
      box-sizing: border-box;
      min-width: 200px;
      max-width: 980px;
      margin: 0 auto;
      padding: 45px;
    }

    @media (max-width: 767px) {
      .markdown-body {
        padding: 15px;
      }
    }

    .footer {
      text-align: center;
    }
  </style>
</head>
<body class="markdown-body">
  <header>
    <h1><a href="${SSGS_URL}/index.html">${SSGS_TITLE}</a></h1>
    <p>${SSGS_DESCRIPTION}</p>
    <nav>${POST_NAVBAR}</nav>
  </header>
  <main>
    ${POST_BODY}
  </main>
  <hr>
  <footer class="footer">
    <p>${SSGS_LICENSE}</p>
    <p>Generated with <a href="${SSGS_REPO}" target="_blank">ssgs</a>, a simple static blog generator written in POSIX <code>sh</code>.</p>
  </footer>
</body>
</html>
EOF
}

# Obtiene la fecha actual dado un formato.
now_date() {
  LC_ALL=C date -u "$1"
}

# Genera el encabezado del feed de atom del blog.
atomfeed_open() { cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${SSGS_LANG}">
<id>${SSGS_URL}/atom.xml</id>
<title>${SSGS_TITLE}</title>
<updated>$(now_date "${DATE_ATOM_FORMAT}")</updated>
<author>
  <name>${SSGS_TITLE}</name>
  <uri>${SSGS_URL}</uri>
</author>
<link rel="self" href="${SSGS_URL}/atom.xml" type="application/atom+xml"/>
<link rel="alternate" href="${SSGS_URL}" type="text/html"/>
<generator uri="${SSGS_REPO}" version="${SSGS_VERSION}">ssgs</generator>
EOF
}

# Genera la etiqueta de cierre del feed de atom del blog.
atomfeed_close() {
  printf '</feed>\n'
}

# Obtiene la fecha de modificación de un archivo dado un formato.
# Si 'date -r' falla, utiliza la fecha actual.
date_file() {
  LC_ALL=C date -u -r "$1" "$2" 2>/dev/null || now_date "$2"
}

# Genera una entrada del feed de atom del blog.
atomfeed_entry() { cat <<EOF
<entry>
  <id>${POST_URL}</id>
  <title>${POST_TITLE}</title>
  <updated>${POST_DATEATOM}</updated>
  <author>
    <name>${SSGS_TITLE}</name>
    <uri>${SSGS_URL}</uri>
  </author>
  <link rel="alternate" href="${POST_URL}" type="text/html"/>
</entry>
EOF
}

# Genera el cuerpo inicial para index.html y posts.html
page_open() {
  printf '<h1>%s</h1>\n<ul>' "$1"
}

# Genera el cierre del cuerpo para index.html y posts.html
page_close() {
  printf '\n</ul>'
}

# Genera cada punto de la lista de artículos.
page_item() {
  printf '\n<li>%s - <a href="%s">%s</a></li>' \
    "${POST_DATE}" "${POST_URL}" "${POST_TITLE}"
}

# Genera el blog.
generate_blog() {
  cd docs || error "Couldn't access 'docs' directory"

  # Construye el navbar del blog.
  POST_NAVBAR=$(navbar)

  HOME_BODY=$(page_open "${SSGS_NEWS}")
  ALL_BODY=$(page_open "${SSGS_ALL}")

  atomfeed_open > atom.xml

  i=0

  for file in $(find . -type f -name \*.md -exec ls -t {} + ||
    error "Couldn't search markdown files")
  do
    # Contruye el PATH del artículo Markdown a HTML.
    POST_PATH=${file%.md}.html

    # Construye la URL del artículo.
    POST_URL=${SSGS_URL}/${POST_PATH#./}

    # Obtiene el contenido del artículo Markdown a HTML.
    POST_BODY=$(markdown_to_html "${file}")

    # Obtiene el título del artículo.
    POST_TITLE=$(get_title "${POST_BODY}")

    # Obtiene la fecha de modificación del artículo.
    POST_DATE=$(date_file "${file}" "${DATE_POST_FORMAT}")

    # Agrega un nuevo artículo en posts.html
    ALL_BODY=${ALL_BODY}$(page_item)

    # Agrega entradas al feed de atom del blog.
    [ "${i}" -lt "${SSGS_LIMIT}" ] && {
      # Agrega un nuevo artículo en index.html
      HOME_BODY=${HOME_BODY}$(page_item)

      # Obtiene la fecha de modificación del artículo para el feed de atom.
      POST_DATEATOM=$(date_file "${file}" "${DATE_ATOM_FORMAT}")

      # Agrega una nueva entrada al feed de atom.
      atomfeed_entry >> atom.xml

      i=$((i + 1))
    }

    # Renombra el archivo Markdown a HTML.
    mv "${file}" "${POST_PATH}"

    # Renderiza la página HTML.
    template > "${POST_PATH}"
  done

  POST_TITLE=${SSGS_TITLE}
  POST_BODY=${HOME_BODY}$(page_close)
  template > index.html

  POST_TITLE=${SSGS_ALL}
  POST_BODY=${ALL_BODY}$(page_close)
  template > posts.html

  atomfeed_close >> atom.xml
}

# Función principal de la aplicación.
main() {
  command -v smu >/dev/null 2>&1 ||
    error "'smu' not found"

  mkdir -p src docs ||
    error "Couldn't create blog directories"

  cp -R -fp src/. docs 2>/dev/null ||
    error "Couldn't access 'src' or 'docs' directory"

  # shellcheck source=/dev/null
  [ -r ssgs.cfg ] && . ./ssgs.cfg

  # Configuración por defecto.
  : "${SSGS_TITLE:=ssgs}"
  : "${SSGS_DESCRIPTION:=A simple static blog generator written in POSIX sh}"
  : "${SSGS_URL:=$PWD/docs}"
  : "${SSGS_LANG:=es}"
  : "${SSGS_CSS:=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown.min.css}"
  : "${SSGS_NEWS:=News}"
  : "${SSGS_ALL:=Posts}"
  : "${SSGS_FEED:=Feed}"
  : "${SSGS_LIMIT:=16}"
  : "${SSGS_LICENSE:=© CC-BY-4.0}"
  : "${SSGS_FAVICON:=data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2210 0 100 100%22><text y=%22.90em%22 font-size=%2290%22>📘</text></svg>}"

  # Formatos de fechas.
  DATE_ATOM_FORMAT=+%Y-%m-%dT%H:%M:%SZ
  DATE_POST_FORMAT=+%Y-%m-%d

  # Elimina diagonales sobrantes del final.
  SSGS_URL=${SSGS_URL%"${SSGS_URL##*[!/]}"}

  generate_blog
}

[ "$1" = -b ] || shelp && main
